今天就來實作圖片上傳的功能及端點!
一開始到 core/models.py 中,因為目前的 Book model 並沒有圖片的欄位,需要先增加欄位並且指定上傳路徑:
import os
import uuid
# ...
def book_image_file_path(instance, filename):
"""Generate file path for new book image."""
ext = os.path.splitext(filename)[1]
filename = f'{uuid.uuid4()}{ext}'
return os.path.join('uploads', 'book', filename)
class Book(models.Model):
# ...
image = models.ImageField(null=True, upload_to=book_image_file_path)
下方的 image 欄位中指定的upload_to
引數會對應到上面的函式,函式內的ext
變數會被賦予該檔案的格式(os.path.splitext()
會將檔案切成陣列,陣列中[1]
即為檔案格式)。接著使用 uuid 產生一組獨特的字元做為新的檔案名。最後會將檔案儲存在uploads/book/
中。此函式的目的為將原本檔案轉換為獨特值並保留檔案格式,避免出現重複的情況。
完成後記得進行資料庫更新:
python manage.py makemigrations
python manage.py migrate
接著來進行 serializer 的製作,進入 book/serializer.py 並修改及新增以下程式碼:
# ...
class BookDetailSerializer(BookSerializer):
"""Serializer for book detail view."""
class Meta(BookSerializer.Meta):
fields = BookSerializer.Meta.fields + ['description', 'image'] # 新增 image field
class BookImageSerialzer(serializers.ModelSerializer):
"""Serializer for uploading images to books."""
class Meta:
model = Book
fields = ['id', 'image']
read_only_fields = ['id']
extra_kwargs = {'image': {'required': True}}
BookDetailSerializer
中增加 image,讓 image 也可顯示在回應中。新增的BookImageSerialzer
裡面指定 id 跟 image 欄位進行序列化,並指定 image 為必填欄位。完成後進入 book/views.py ,要再次修改get_serializer_class
、新增導入項目以及新增程式碼如下:
from rest_framework import viewsets, status # 新導入 status
from rest_framework.decorators import action
from rest_framework.response import Response
# ...
class BookViewSet(viewsets.ModelViewSet):
# ...
def get_serializer_class(self):
"""Return the serializer class for request."""
if self.action == 'list':
return serializers.BookSerializer
elif self.action == 'upload_image':
return serializers.BookImageSerialzer
return self.serializer_class
@action(methods=['POST'], detail=True, url_path='upload_image')
def upload_image(self, request, pk=None):
"""Upload an image to book."""
book = self.get_object()
serializer = self.get_serializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.error, status=status.HTTP_400_BAD_REQUEST)
這邊在get_serializer_class
新增上傳圖片的條件。但預設 action 並無 upload_image,所以需要自定義在下方。函式上方的裝飾器裡,methods=['POST']
表示只接受 POST 請求,detail=True
表示此功能僅作用在特定一物件上;若detail=False
則會作用在所有物件,整體來說,若要觸發 POST, API 請求中的 URL 需要包含書籍的主鍵(pk)和 'upload_image' 路徑,例如 /books/1/upload_image/。
函式內部首先先用get_object()
取得該物件,並用此物件及請求中的資料初始化 serializer(就是呼叫get_serializer_class()
),再來驗證資料正確性,若正確儲存資料並回傳 200;反之則回傳 400。
以上儲存後,到 rest_api/settings.py 中新增:
# ...
SPECTACULAR_SETTINGS = {
'COMPONENT_SPLIT_REQUEST': True,
}
這樣在上傳檔案時才會出現選擇檔案的按鈕。
都儲存後,就打開伺服器測試看看吧
在 Swagger 介面中,
upload_image
的端點要使用multipart/formdata
才能正確上傳
到這邊,book RESTful API 算是完成了,而本次鐵人賽所有的作品也如期完成,最後來個小總結,明天見~